#include <jni.h>

#include "can_config.h"

#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <stdint.h>

#include <net/if.h>

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>

#include <linux/can.h>
#include <linux/can/raw.h>

#include <Android/log.h>
#define TAG "JinLog"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)


#define BUF_SIZ	(255)

static int total_count = 0;
static int m_bReceive = 0;


// processing callback to handler class
typedef struct pudu_context {
    JavaVM  *javaVM;
    jclass   jniCanDataCallbackClz;
    jobject  jniCanDataCallbackObj;
    jmethodID callbackFunc;
} PuduContext;
PuduContext g_ctx;

JNIEXPORT jint JNICALL
Java_com_pudu_socketcan_CanBusNative_startReceive(JNIEnv *env, jclass classtype, jobject callback) {

    int n = 0;
    int nbytes,i;
    struct ifreq ifr;
    FILE *out = stdout;
    struct can_frame frame;
    char *interface = "can0";
    struct sockaddr_can addr;
    char buf[BUF_SIZ];
    int	s = -1;

    int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW;

    m_bReceive = 1;

    if ((s = socket(family, type, proto)) < 0) {
        LOGD("strerror: %s\n", strerror(errno));
        return 1;
    }

    addr.can_family = family;
    strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
    if (ioctl(s, SIOCGIFINDEX, &ifr)) {
        LOGD("strerror: %s\n", strerror(errno));
        return 1;
    }
    addr.can_ifindex = ifr.ifr_ifindex;

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        LOGD("strerror: %s\n", strerror(errno));
        return 1;
    }

    jbyte aByte[8];
    jbyteArray jbarray = (*env)->NewByteArray(env,8);

    while (m_bReceive) {

        if ((nbytes = read(s, &frame, sizeof(struct can_frame))) < 0) {
            LOGD("strerror: %s\n", strerror(errno));
            return 1;
        }else {
            if( frame.can_id == 0x0101)
            {
                LOGD("myself send.\n");
                continue;
            }

            if( frame.can_dlc == 8) {
                aByte[0] = frame.data[0];
                aByte[1] = frame.data[1];
                aByte[2] = frame.data[2];
                aByte[3] = frame.data[3];
                aByte[4] = frame.data[4];
                aByte[5] = frame.data[5];
                aByte[6] = frame.data[6];
                aByte[7] = frame.data[7];

                (*env)->SetByteArrayRegion(env, jbarray, 0, 8, aByte);
                (*env)->CallVoidMethod(env, callback, g_ctx.callbackFunc, jbarray);
            }
        }
    }

    (*env)->DeleteLocalRef(env, jbarray);

    close(s);

    return 0;
}

JNIEXPORT jint JNICALL
Java_com_pudu_socketcan_CanBusNative_stopReceive(JNIEnv *env, jclass type) {

    // TODO
    m_bReceive = 0;
    return 0;
}


JNIEXPORT jint JNICALL
Java_com_pudu_socketcan_CanBusNative_sendBytes(JNIEnv *env, jclass classtype, jbyteArray data_) {

    struct can_frame frame = {
            .can_id = 0x0101,
    };

    int	s = -1;
    int loopcount = 1, infinite = 0;
    int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW;
    char *interface = "can0";
    struct sockaddr_can addr;
    struct ifreq ifr;
    int ret;

    int len = (*env)->GetArrayLength (env,data_);
    if(len < 8)
    {
        return 1;
    }

    s = socket(family, type, proto);
    if (s < 0) {
        LOGD("strerror: %s\n", strerror(errno));
        return 1;
    }

    addr.can_family = family;
    strcpy(ifr.ifr_name, interface);
    if (ioctl(s, SIOCGIFINDEX, &ifr)) {
        LOGD("strerror: %s\n", strerror(errno));
        return 1;
    }

    addr.can_ifindex = ifr.ifr_ifindex;

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        LOGD("strerror: %s\n", strerror(errno));
        return 1;
    }

    jbyte *data = (*env)->GetByteArrayElements(env, data_, NULL);
    frame.data[0] = data[0];
    frame.data[1] = data[1];
    frame.data[2] = data[2];
    frame.data[3] = data[3];
    frame.data[4] = data[4];
    frame.data[5] = data[5];
    frame.data[6] = data[6];
    frame.data[7] = data[7];
    frame.can_dlc = 8;

    frame.can_id &= CAN_SFF_MASK;

    while (infinite || loopcount--) {
        ret = write(s, &frame, sizeof(frame));
        if (ret == -1) {
            LOGD("strerror: %s\n", strerror(errno));
            break;
        }
    }

    (*env)->ReleaseByteArrayElements(env, data_, data, 0);

    close(s);
    return 0;
}

/*
 * processing one time initialization:
 *     Cache the javaVM into our context
 *     Find class ID for JniHelper
 *     Create an instance of JniHelper
 *     Make global reference since we are using them from a native thread
 * Note:
 *     All resources allocated here are never released by application
 *     we rely on system to free all global refs when it goes away;
 *     the pairing function JNI_OnUnload() never gets called at all.
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    memset(&g_ctx, 0, sizeof(g_ctx));

    g_ctx.javaVM = vm;
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR; // JNI version not supported.
    }

    jclass  clz = (*env)->FindClass(env,
                                    "com/pudu/socketcan/CanDataCallback");
    g_ctx.jniCanDataCallbackClz = (*env)->NewGlobalRef(env, clz);

    g_ctx.callbackFunc = (*env)->GetMethodID(env, clz,
                                            "onCanReceive", "([B)V");

    LOGD("JNI_OnLoad\n");


    return  JNI_VERSION_1_6;
}